#pragma once

#include <string>
#include <vector>
#include "../transport.h"
#include "common/uuid.h"
#include "common/date.h"


namespace evm { namespace messaging {

using namespace std;

struct NotifierDriver {
    NotifierDriver(vector<string>&& topics, TransportPtr transport)
        : topics(std::move(topics)), transport(transport) {}

    virtual void notify(Json&& msg, const string& priority, int retry) = 0;

    vector<string> topics;
    TransportPtr transport;
};

using NotifierDriverPtr = shared_ptr<NotifierDriver>;

/* Sends notifications over the configured messaging transport.
 */
struct MessagingDriver : NotifierDriver {
    MessagingDriver(vector<string>&& topics, TransportPtr transport)
    : NotifierDriver(std::move(topics), transport) {}

    void notify(Json&& msg, const string& priority, int retry) override;
};


vector<NotifierDriverPtr>
get_notifier_drivers(TransportPtr transport, const string& topic = string())
{
    ConfigOptsPtr conf = transport->conf;

    auto& notifier_driver = (*conf)["rpc_notifier_driver"];
    if (!notifier_driver.is_array())
        throw invalid_argument("Option rpc_notifier_driver should be a list.");

    vector<string> topics;
    if (topic.empty()) {
        auto& notifier_topics = (*conf)["rpc_notifier_topics"];
        if (!notifier_topics.is_array())
            throw invalid_argument("Option rpc_notifier_topics should be a list.");
        topics = notifier_topics.get<vector<string>>();
    } else
        topics.push_back(topic);

    vector<NotifierDriverPtr> drivers;

    if (evm::find(notifier_driver, "messaging")) {
        auto driver = NotifierDriverPtr(new MessagingDriver(std::move(topics), transport));
        drivers.push_back(driver);
    }

    return drivers;
}


struct Notifier;
using NotifierPtr = shared_ptr<Notifier>;

/* Send notification messages. */
struct Notifier {
    Notifier(TransportPtr transport, const string& publisher_id,
             const string& topic = string(), int retry = -1)
    : transport(transport), publisher_id(publisher_id), retry(retry)
    {
        drivers = get_notifier_drivers(transport, topic);
    }

    Notifier(TransportPtr transport, const string& publisher_id,
             int retry, vector<NotifierDriverPtr> drivers)
    : transport(transport), publisher_id(publisher_id), retry(retry), drivers(drivers) {}

    /* Return new notifier, with overridden publisher_id, retry. */
    NotifierPtr prepare(const string& publisher_id,  int retry = -1) {
        return make_shared<Notifier>(transport, publisher_id, retry, drivers);
    }

    /* Send a notification at audit level. */
    void audit(const string& event_type, const Json& payload) {
        notify(event_type, payload, "AUDIT");
    }

    /* Send a notification at debug level. */
    void debug(const string& event_type, const Json& payload) {
        notify(event_type, payload, "DEBUG");
    }

    /* Send a notification at info level. */
    void info(const string& event_type, const Json& payload) {
        notify(event_type, payload, "INFO");
    }

    /* Send a notification at warn level. */
    void warn(const string& event_type, const Json& payload) {
        notify(event_type, payload, "WARN");
    }

    /* Send a notification at error level. */
    void error(const string& event_type, const Json& payload) {
        notify(event_type, payload, "ERROR");
    }

    /* Send a notification at critical level. */
    void critical(const string& event_type, const Json& payload) {
        notify(event_type, payload, "CRITICAL");
    }

    /* Send a notification at sample level.
     * Sample notifications are for high-frequency events
     * that typically contain small payloads. eg: "CPU = 70%"
     *
     * Not all drivers support the sample level
     * (log, for example) so these could be dropped.
     */
    void sample(const string& event_type, const Json& payload) {
        notify(event_type, payload, "SAMPLE");
    }

private:
    void notify(const string& event_type, const Json& payload,
                const string& priority, const string& publisher_id = string(), int retry = -1) {
        Json msg = {
            {"message_id", uuid::to_str()},
            {"publisher_id", publisher_id.empty() ? this->publisher_id : publisher_id},
            {"event_type", event_type},
            {"priority", priority},
            {"payload", payload},
            {"timestamp", Date()}
        };
        for (auto& driver: drivers)
            driver->notify(std::move(msg), priority, retry == -1 ? this->retry : retry);
    }

    TransportPtr transport;
    string publisher_id;
    int retry;
    vector<NotifierDriverPtr> drivers;
};

}}
